﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ServiceDesk.DAL;
using NHibernate;
using NHibernate.Linq;
using ServiceDeskInterop;



namespace ServiceDesk.Model
{
    public class SessionModel
    {
        public SessionModel(ISessionFactory sessionFactory)
        {
            SessionFactory = sessionFactory;//


        }
        #region test
        public void TestInit()
        {
            WithTransaction(session =>
                AddUserRaw(session, "adm", "1", UserRole.Admin));

            LogIn("adm", "1");

            var taskList = new List<TaskDTO>();
            foreach (var d in Enumerable.Range(0, 20).Select(_ => "Descr " + _.ToString()))
            {
                taskList.Add(TestCreateTask(d));
            }

            var user = AddUser("usr", "1", UserRole.Operator);
            
        }
        #endregion

        private T WithTransaction<T>(Func<ISession,T> func)
        {
            using (var session = SessionFactory.OpenSession())
            {
                using (var tx = session.BeginTransaction())
                {
                    var res = func(session);
                    tx.Commit();
                    return res;
                }
            }
        }
        private void WithTransaction(Action<ISession> func)
        {
            using (var session = SessionFactory.OpenSession())
            {
                using (var tx = session.BeginTransaction())
                {
                    func(session);
                    tx.Commit();
                }
            }
        }
        private struct LogInfo
        {
            public LogInfo(Guid userGuid ,int role)
            {
                Role = role;
                UserGuid = userGuid;
            }
            public readonly int Role;
            public readonly Guid UserGuid;
        }
        private LogInfo? _LogInfo;

       


        #region Validations
        public void ValidateIsLogged()
        {
            if (!_LogInfo.HasValue )
                throw new ValidationException(ValidationException.MustBeLoggedRule);
        }
        private void ValidateIsAdmin()
        {
            ValidateIsLogged();
            if (_LogInfo.Value.Role != UserRole.Admin)
            {
                throw new ValidationException(ValidationException.MustBeAdminRule);
            }
        }
        private void ValidateNotEmptyPassword(string password)
        {
            if (string.IsNullOrEmpty(password))
                throw new ValidationException(ValidationException.NotEmptyPasswordRule);
        }
        private void ValidatePasswordLength(string password)
        {
            if(password.Length>User.MaxPasswordLength)
                throw new ValidationException(ValidationException.MaxPaswordLengthRule);
        }
        private void ValidateNotEmptyLogin(string login)
        {
            if (string.IsNullOrEmpty(login))
                throw new ValidationException(ValidationException.NotEmptyLoginRule);
        }
        private void ValidateLoginLength(string login)
        {
            if (login.Length > User.MaxLoginLength)
                throw new ValidationException(ValidationException.MaxLoginLengthRule);
        }

        private void ValidateIsUniqueLogin(IEnumerable<string> existedLoggins, string newLoggin)
        {
            if (existedLoggins.Contains(newLoggin))
                throw new ValidationException(ValidationException.MustBeUniqueLoginRule);
            
        }
        private void ValidateRoleMustExist(int role)
        {
            if (!UserRole.AllRoles.Contains(role))
                throw new ValidationException(ValidationException.RoleMustExist);
        }
        private void ValidateAssigneeMustBeOperator(User user)
        {
            if (user.UserRole != UserRole.Operator)
                throw new ValidationException(ValidationException.AssigneeMustBeOperatorRule);
        }
        private void ValidateTaskStateMustExist(int taskState)
        {
            if (!TaskState.AllStates.Contains(taskState))
                throw new ValidationException(ValidationException.TaskStateMustExistRule);
        }
        private void ValidateCurrentUserCanSetTaskState(Task task)
        {
            ValidateIsLogged();
            var logInfo = _LogInfo.Value;
            if (logInfo.Role == UserRole.Admin)
                return;


            if (
                (task.User != null) &&
                (logInfo.UserGuid == task.User.Guid))
                return;
            throw new ValidationException(ValidationException.OperatorCanOnlyChangeTheirAssignmentsRule);
        }
        #endregion

        public  LoginDTO LogIn(string login, string password)
        {
            using (var session = SessionFactory.OpenSession())
            {
                var user = session.Query<User>()
                                    .Where(_ => _.Login == login)
                                    .Where(_ => _.Password == password)
                                    .FirstOrDefault();
                if (user == null)
                    throw new ModelException(ModelException.WrongCredentials);

                _LogInfo = new LogInfo(user.Guid, user.UserRole);

                return new LoginDTO(RoleToDTO( user.UserRole), user.Guid);

            }
        }

        public UserDTO AddUser(string login, string password, RoleDTO role)
        {
            ValidateIsAdmin();
            ValidateNotEmptyLogin(login);
            ValidateNotEmptyPassword(password);
            ValidateRoleMustExist( RoleFromDTO( role));// эта проверка уже не требуется
            ValidatePasswordLength(password);
            ValidateLoginLength(login);

            return
                 WithTransaction(session =>
                     {
                         var existedLogins = session.Query<User>().Select(_ => _.Login);
                         ValidateIsUniqueLogin(existedLogins,login);
                         return AddUserRaw(session,login,password, RoleFromDTO( role));
                     });

        }
        private static Dictionary<RoleDTO,int> RolesDict = new List<System.Tuple<RoleDTO,int>>()
            {
                Tuple.Create(RoleDTO.Admin, UserRole.Admin),
                Tuple.Create(RoleDTO.Operator,UserRole.Operator)
            }.ToDictionary(_=>_.Item1,_=>_.Item2);

        private static RoleDTO RoleToDTO(int role)
        {
            return RolesDict.First(_ => _.Value == role).Key;
        }
        private static int RoleFromDTO(RoleDTO role)
        {
            return RolesDict[role];
        }


        private UserDTO AddUserRaw(ISession session, string login, string password, int role)
        {
                var newUser = new User() {Login = login, Password = password, UserRole = role};
                session.Save(newUser);                    
                return new UserDTO( newUser.Guid,newUser.Login, RoleToDTO(newUser.UserRole));    
        }
        public bool RemoveUser(Guid userGuid)
        {
            ValidateIsAdmin();
            return
                WithTransaction(session =>
                {
                    var user = session.Query<User>().Where(_ => _.Guid == userGuid).FirstOrDefault();

                    if (user == null)
                        return false;

                    
                    RemoveUserRaw(session, user);
                    return true;
                });
        }

        private void RemoveUserRaw(ISession session, User user)
        {
                        
            var tasksAssignedToUser = (from task in session.Query<Task>() where task.User == user select task).Fetch(t=>t.User).ToList();
            foreach(var task in tasksAssignedToUser)
            {
                task.User = null;// TODO рассмотреть возможность отложенного изменения.
                session.Save(task);
            }

            session.Delete(user);

            return;
                     
        }

        public void Assign(Guid taskGuid, Guid userGuid)
        {
            ValidateIsAdmin();
            WithTransaction(session=>
                {
                    var task = session.Query<Task>().Where(_=>_.Guid == taskGuid).FirstOrDefault();
                    if(task==null)
                        throw new ModelException(ModelException.TaskNotExist);


                    var user = session.Query<User>().Where(_=>_.Guid == userGuid).FirstOrDefault();
                    if(user == null)
                        throw new ModelException(ModelException.UserNotExist);
                    ValidateAssigneeMustBeOperator(user);
                    AssignRaw(session,task,user);
                });
        }
        private void AssignRaw(ISession session, Task task, User user)
        {
            task.User = user;
            session.Save(task);
        }
        public void SetTaskState(Guid taskGuid, TaskStateDTO taskState)
        {
            ValidateIsLogged();


            WithTransaction(session =>
                {
                    var task = session.Query<Task>().Where(t=>t.Guid==taskGuid).Fetch(_ => _.User).FirstOrDefault();
                    if (task == null)
                        throw new ModelException(ModelException.TaskNotExist);
                    ValidateCurrentUserCanSetTaskState(task);
                    SetTaskStateRaw(session,task,TaskStateFromDTO(taskState));
                });

        }
        private void SetTaskStateRaw(ISession session, Task task, int taskState)
        {
            task.TaskState = taskState;
            session.Save(task);
        }


        public IEnumerable<UserDTO> ListUsers()
        {
            ValidateIsLogged();
            using (var session = SessionFactory.OpenSession())
            {
                return
                    ListUsersRaw(session);
            }
        }
        private IEnumerable<UserDTO> ListUsersRaw(ISession session)
        {
            return
                session.Query<User>().Select(_=>new UserDTO(_.Guid,_.Login, RoleToDTO( _.UserRole))).ToList();
        }


        public IEnumerable<TaskDTO> ListTasks()
        {
            ValidateIsLogged();
            using (var session = SessionFactory.OpenSession())
            {
                return ListTasksRaw(session);
            }
        }

        private static Dictionary<TaskStateDTO, int> TaskStateDict = new List<System.Tuple<TaskStateDTO, int>>()
            {
                Tuple.Create(TaskStateDTO.Open, TaskState.Open),
                Tuple.Create(TaskStateDTO.InProgress, TaskState.InProcess),
                Tuple.Create(TaskStateDTO.Closed, TaskState.Closed)
            }.ToDictionary(_ => _.Item1, _ => _.Item2);

        private static TaskStateDTO TaskStateToDTO(int state)
        {
            return TaskStateDict.First(_ => _.Value == state).Key;
        }
        private static int TaskStateFromDTO(TaskStateDTO state)
        {
            return TaskStateDict[state];
        }
        private IEnumerable<TaskDTO> ListTasksRaw(ISession session)
        {
            return
                session.Query<Task>().Fetch(_ => _.User)
                .Select(
                        _ => new TaskDTO(
                                _.Guid,
                                _.Description,
                                TaskStateToDTO(_.TaskState),
                                (_.User != null) ? _.User.Guid:null as Guid? ))
                                .ToList();
        }
        #region Test

        public TaskDTO TestCreateTask(string description)
        {
            return
                WithTransaction(session =>
                    {
                        var task = new Task() { Description = description };
                        session.Save(task);
                        return new TaskDTO( task.Guid,task.Description, TaskStateToDTO( task.TaskState),null)  ;
                    });
        }
        #endregion


        private readonly ISessionFactory SessionFactory;

    }
}
